package com.ershi.spring.annotation;

import com.ershi.spring.annotation.bean.Child;
import com.ershi.spring.annotation.config.SpringConfig;

import com.ershi.spring.annotation.di.HelloService;
import com.ershi.spring.annotation.imports.Cat;
import com.ershi.spring.annotation.imports.Dog;
import com.ershi.spring.annotation.imports.Red;
import com.ershi.spring.annotation.lazy.LazyBean;
import com.ershi.spring.annotation.lookup.Children;
import com.ershi.spring.annotation.lookup.Root;
import com.ershi.spring.annotation.values.Girl;
import com.ershi.spring.annotation.values.ValueBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author 二十
 * @since 2021/10/30 8:06 下午
 * 测试spring的一些注解和组件
 * 本次分享内容，主要从两方面入手：
 * 1.注解
 * 2.组件
 * 通过穿插源码来看组件的执行时机，从而更直观的了解组件的作用时机 & 应用场景。
 * ? ioc容器创建源码分析
 * ？ aop整体流程分析
 * ？ 手写aop实现
 * ？ aop源码逐行分析
 */
public class AnnotationMainTest {

	/**
	 * basepackage:"com.yhd.annotation"
	 */
	private static ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfig.class);

	public static void main(String[] args) {
		/*测试@Bean注解*/
		//testBean();

		/*测试@Import注解的使用方式一 倒入容器之后，他的别名仍然是全限定类名*/
		//testImport01();

		/*测试@Import注解的使用方式二*/
		//testImport02();

		/*测试@Import注解的使用方式三*/
		//testImport03();

		/*测试@Lookup注解和@Scope注解和@Component，@Service，@Repository，@Controller*/
		testLookUp();

		/*@Lazy注解*/
		//testLazy();

		/*@Conditional*/
		//testConditional();

		/*@Value*/
		//testValue();

		/*@PropertySource*/
		//testPropertySource();

		/*@Autowired  @Qualifier @Primary*/
		//testAutowiredVsQualifier();

	}

	/**
	 * 测试@Bean注解
	 * 注意：让销毁方法执行，必须手动调用ioc.close()
	 *
	 */
	private static void testBean(){
		Child child = ioc.getBean("child", Child.class);
		System.out.println("child = " + child);
		((AnnotationConfigApplicationContext)ioc).close();
	}

	/**
	 * 测试import直接导入bean到容器中
	 *
	 */
	private static void testImport01(){
		Dog dog = ioc.getBean(Dog.class);
		System.out.println("dog = " + dog);
	}

	/**
	 * 测试通过 importBeanDefinitionRegister 导入bean 到容器中
	 */
	private static void testImport02(){
		Cat tomCat = ioc.getBean("tomCat", Cat.class);
		System.out.println("tomCat = " + tomCat);
	}

	/**
	 * 测试通过importSelector 导入bean到容器中
	 */
	private static void testImport03(){
		Red red = ioc.getBean(Red.class);
		System.out.println("red = " + red);
	}

	/**
	 * 首先第一行打印打印的是false，为什么是false？因为在SpringConfig中以@Bean的方式注入了Children对象
	 * 并且通过@Scope注解指定了这个组件是原型模式，也就是说，每一次从容器中，拿到的都是一个全新的对象，不走三级缓存
	 *
	 * 再看第二行打印：默认情况下，第二行打印是true，为什么是true？
	 * 因为root bean是单例的，单例bean的创建的时候会通过populateBean()进行属性的注入，创建完成后这个root实例被三级缓存所缓存
	 * 所以，两次拿到的root对象实际上是同一个，同一个对象的属性值肯定相同
	 * 在root的getChild方法上加上@Lookup注解以后，为什么就打印false了？
	 * 这样会在拿到bean对象以后进行一个操作，依赖检查  ， 检查的时候发现  root 依赖的children对象上面标注了@lookupzhu注解，
	 * 所以依赖会被重新注入，虽然root对象仍然是同一个，但是root对象的依赖被重新注入了，不是同一个引用，所以打印的是false。
	 *
	 *
	 * 注意：这里有个坑，想要@Lookup注解生效，那么root组件必须是@Compoment注解添加到容器的，不能是@Bean注解添加到容器的，看源码
	 * please remember that lookup methods won't work on beans returned from {@code @Bean} methods in configuration classes
	 *
	 */
	private static void testLookUp(){
		Children child1 = ioc.getBean(Children.class);
		Children child2 = ioc.getBean(Children.class);
		System.out.println(child1 == child2); //false
		Root root1 = ioc.getBean(Root.class);
		Root root2 = ioc.getBean(Root.class);

		System.out.println(root1.getChild() == root2.getChild()); // true
		System.out.println(root1.getChild().hashCode());
		System.out.println(root2.getChild().hashCode());
	}

	/**
	 * 这里可以直接看一下refresh的第十一个方法 初始化剩下的所有的单实例bean
	 *
	 * 在创建bean的时候会进行判断？当前bean是不是单例的？懒加载的，
	 */
	private static void testLazy(){
		/*在这一行打断点看bf->singltonObjects ,此时没有lazyBean。*/
		LazyBean lazyBean = ioc.getBean("lazyBean", LazyBean.class);
		/*真正用到的时候才会去加载*/
		System.out.println("lazyBean = " + lazyBean);
	}

	private static void testConditional(){
		String osName = ioc.getEnvironment().getProperty("os.name");
		//Mac OS X
		System.out.println("osName = " + osName);
		System.out.println(ioc.containsBean("conditionalBean"));
	}

	/**
	 * 关于SPEL表达式：
	 *
	 * https://www.yuque.com/yinhuidong/spring/zg8g94
	 */
	private static void testValue() {
		ValueBean valueBean = ioc.getBean(ValueBean.class);
		System.out.println("valueBean.getName() = " + valueBean.getName());
		System.out.println("valueBean.getAge() = " + valueBean.getAge());
	}

	private static void testPropertySource() {
		Girl girl = ioc.getBean(Girl.class);
		System.out.println("girl = " + girl);
	}

	/**
	 * @Autowired  自动按照类型进行装配,如果有多个，优先按照属性名进行装配，但是假如属性名都不匹配，就会报错，spring不知道应该装配哪一个
	 * @Qualifier  当使用Autowired进行自动装配的时候，假如同一个类型在容器中有多个的时候
	 * 容器就不知道应该装配哪一个，这个时候，使用此注解可以指定想要装配的bean名字。
	 *
	 * 默认一定得装配一个，找不到就会报错，可以通过 required =false 设置 找不到也不报错。
	 *
	 * @Resource 可以和@Autowired一样实现自动装配功能，默认是按照组件名称进行装配的。
	 * 没有能支持@Primary功能没有支持@Autowired(required=false)
	 */
	private static void testAutowiredVsQualifier() {

		HelloService service = ioc.getBean(HelloService.class);
		System.out.println("service = " + service);
	}
}
